<?php

namespace ayhome\crontab\command;

use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\facade\Cache;
use think\facade\Config;
use Swoole\Process;

use ayhome\crontab\CronExpression;

class Crontab extends Command
{
  protected $config = [];
  protected $pid;

  public function configure()
  {
    $this->setName('crontab')
      ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start')
      ->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the swoole server in daemon mode.')
      ->setDescription(' crontab base Swoole for ThinkPHP');
  }

  public function execute(Input $input, Output $output)
  {
    $action = $input->getArgument('action');
    
    $this->config = Config::get('crontab.');
    if (in_array($action, ['start', 'stop', 'reload', 'restart'])) {
      $this->$action();
    } else {
      $output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload .");
    }
  }

  /**
   * 启动server
   * @access protected
   * @return void
   */
  protected function start()
  {
    $pid = $this->getMasterPid();

    // print_r($pid);

    if ($this->isRunning($pid)) {
      $this->output->writeln('swoole crontab server process is already running.');
      exit(1);
    }

    $this->output->writeln('Starting crontab server...');

    // 开启守护进程模式
    if ($this->input->hasOption('daemon')) {
      \Swoole\Process::daemon();
    }
    $this->ppid    = getmypid();

    $this->output->writeln("crontab started");
    $this->output->writeln('You can exit with <info>`CTRL-C`</info>');

    $crontab = $this->config['crontab'];
    swoole_timer_tick(1000, function ()use($crontab) {
      foreach ($crontab as $k) {
        $cr = CronExpression::parse($k['cron']);
        $now = date('s');
        if (isset($cr[$now])) {
          $this->runTask($k);
        }
      }
    });
  }

  public function runTask($task = array())
  {
    $reserveProcess = new \Swoole\Process(function ($worker) use ($workNum, $workOne,$task) {
      usleep($this->sleepTime);
      $this->checkMpid($worker);
      try {

        $path_arr = explode("\\", $task['bin']);

        $group = $path_arr[1];
        $controller = ucwords(strtolower($path_arr[2]));
        $action = $path_arr[3];
        
        $cls = "\\app\\{$group}\\controller\\{$controller}";

        if (!class_exists($cls)) {
          echo("{$cls} 类不存在 \n");
        }
        if (!method_exists($cls, $action)) {
          echo("{$cls}\{$cls} 方法不存在 \n");
        }
        $bindParams = $k['binArgs'];
        $ctrl = new $cls;
        $r = $ctrl->$action($bindParams);
        // $ret = call_user_func_array([$ctrl, 'index'], $bindParams);
        // echo $cls;
        //执行一个外部程序
        // $worker->exec($workOne['bin'], $workOne['binArgs']);
      } catch (\Throwable $e) {
        echo $e -> getMessage() . "\n";
      } catch (\Exception $e) {
        echo $e -> getMessage() . "\n";
      }
      // $this->logger->log('worker id: ' . $workNum . ' is done!!!', 'info', $this->logSaveFileWorker);
      $worker->exit(0);
    });
    $pid                                        = $reserveProcess->start();
    $this->workers[$pid]                        = $reserveProcess;
    // $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $task['name'], $pid, 'add');
    // $this->workersByPidName[$pid]               =$task['name'];
    // $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $task['name'] =>self::STATUS_RUNNING]);
    // $this->logger->log('worker id: ' . $workNum . ' pid: ' . $pid . ' is start...', 'info', $this->logSaveFileWorker);
  }

  //主进程如果不存在了，子进程退出
    private function checkMpid(&$worker)
    {
      // if (!@\Swoole\Process::kill($this->ppid, 0)) {
        // $worker->exit();
        // $this->logger->log("Master process exited, I [{$worker['pid']}] also quit");
      // }
    }

  /**
   * 柔性重启server
   * @access protected
   * @return void
   */
  protected function reload()
  {
        // 柔性重启使用管理PID
    $pid = $this->getManagerPid();

    if (!$this->isRunning($pid)) {
      $this->output->writeln('no crontab server process running.');
      exit(1);
    }

    $this->output->writeln('Reloading swoole_http_server...');
    Process::kill($pid, SIGUSR1);
    $this->output->writeln('> success');
  }

  /**
   * 停止server
   * @access protected
   * @return void
   */
  protected function stop()
  {

    $pid = $this->getMasterPid();

    if (!$this->isRunning($pid)) {
      $this->output->writeln('no swoole http server process running.');
      exit(1);
    }

    $this->output->writeln('Stopping swoole http server...');

    Process::kill($pid, SIGTERM);
    $this->removePid();

    $this->output->writeln('> success');
  }

  /**
   * 重启server
   * @access protected
   * @return void
   */
  protected function restart()
  {
    $pid = $this->getMasterPid();

    if ($this->isRunning($pid)) {
      $this->stop();
    }

    $this->start();
  }

  /**
   * 获取主进程PID
   * @access protected
   * @return int
   */
  protected function getMasterPid()
  {

    $masterPid = Cache::get('swoole_crontab_pid');

    if ($masterPid) {
      return $masterPid;
    }

    $pidFile = $this->config['pid_file'];

    if (is_file($pidFile)) {
      $masterPid = (int)file_get_contents($pidFile);
      Cache::set('swoole_crontab_pid', $masterPid);
    }

    return $masterPid;
  }

  /**
   * 获取管理进程PID
   * @access protected
   * @return int
   */
  protected function getManagerPid()
  {
    $managerPid = Cache::get('crontab_manager_pid');

    if ($managerPid) {
      return $managerPid;
    }

    $pidFile = dirname($this->config['pid_file']) . '/swoole_manager.pid';

    if (is_file($pidFile)) {
      $managerPid = (int)file_get_contents($pidFile);
      Cache::set('crontab_manager_pid', $managerPid);
    }

    return $managerPid;
  }

  /**
   * 删除PID文件
   * @access protected
   * @return void
   */
  protected function removePid()
  {
    $masterPid = $this->config['pid_file'];

    if (is_file($masterPid)) {
      unlink($masterPid);
    }

    $managerPid = dirname($this->config['pid_file']) . '/swoole_manager.pid';

    if (is_file($managerPid)) {
      unlink($managerPid);
    }

    Cache::rm('swoole_crontab_pid');
    Cache::rm('crontab_manager_pid');
  }

  /**
   * 判断PID是否在运行
   * @access protected
   * @param  int $pid
   * @return bool
   */
  protected function isRunning($pid)
  {
    if (empty($pid)) {
      return false;
    }

    Process::kill($pid, 0);

    return !swoole_errno();
  }
}